/*******************************************************************************
 * Copyright (C) 2010 Devin Samarin.
 * 
 * This file is part of the SUIT Toolkit.
 * 
 * The SUIT Toolkit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * The SUIT Toolkit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SUIT Toolkit.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package suit.ui;

import java.util.Vector;

import javax.microedition.lcdui.Graphics;

/**
 * The base class of all SUIT Widgets. Graphical user interface toolkits have
 * long been built up from individual controls and presentation mechanisms that
 * are nested together. These elements are collectively called Widgets. Each
 * Widget may contain any number of Widgets as a child.
 * 
 * @author Devin Samarin
 * 
 */
public abstract class Widget {

	public static final int FOCUS_TRUE = 1;
	public static final int FOCUS_FALSE = 2;
	public static final int FOCUS_CHILDREN = 4;

	private Rectangle clip;

	protected boolean realized;

	// private boolean sensitive; /* Indicates whether widget can be used */
	// private boolean shown = true; /* Indicates whether widget is shown */

	protected boolean focused = false;
	protected int focusable = FOCUS_FALSE;
	public void activate() {};

	protected Rectangle allocation;

	private boolean entered = false; /*
									 * If the pointer is currently within the
									 * allocation of the widget
									 */

	protected boolean hasChildren;

	protected boolean isRoot = false;
	protected Screen screen;
	protected Widget parent;

	protected boolean needsUpdate;

	protected boolean expand; // These three are only for use with Box()
	protected boolean fill;
	protected int padding;

	protected Vector children;
	protected int borderWidth = 0;

	protected Widget() {
		realized = false;
		needsUpdate = true;
		hasChildren = false;
		borderWidth = 0;
	}
	
	protected void touch() {
		screen.display.vibrate(20);
	}

	/**
	 * Add a child Widget to this Widget. This works for all Widgets, but does
	 * not offer you to control positioning and layout with as much control.
	 * 
	 * @param child
	 *            the Widget to add as a child
	 */
	public void add(Widget child) {
		if (!hasChildren) {
			hasChildren = true;
			children = new Vector();
		}
		children.addElement(child);
		child.parent = this;
		child.setScreen(screen);
		child.isRoot = false;
		invalidate(this);
	}
	
	public void setScreen(Screen screen) {
		this.screen = screen;
		if (hasChildren) {
			int size = children.size();
			for (int i = 0; i < size; i++) {
				((Widget) children.elementAt(i)).setScreen(screen);
			}
		}
	}

	/**
	 * Remove a Widget from this Widget.
	 * 
	 * @param child
	 *            a child Widget that's already in the Widget.
	 */
	public void remove(Widget child) {
		child.realized = false;
		child.parent = null;
		children.removeElement(child);
		if (children.size() == 0) {
			hasChildren = false;
		}
		invalidate(this);
	}

	/**
	 * Get the Widgets that are children of this Widget, i.e. the Widgets
	 * previously added to the Widget. An Vector of Widgets isn't always ideal,
	 * but you can cast the returned objects to the appropriate Widget subtype
	 * should you need to.
	 * 
	 * @return a Vector of the Widget's children.
	 */
	public Vector getChildren() {
		return children;
	}

	/**
	 * Set the amount of padding to put around the outside of this Widget, in
	 * pixels. This is exterior padding around the outside of the contained
	 * Widgets. The default is 0.
	 * 
	 * @param width
	 *            the amount of padding
	 */
	public void setBorderWidth(int width) {
		this.borderWidth = width;
	}

	protected boolean onLongPress(Event e) {
		return true;
	};

	protected boolean onPointerPressed(Event e) {
		return true;
	};

	protected boolean onPointerDragged(Event e) {
		return true;
	};

	protected boolean onPointerReleased(Event e) {
		return true;
	};

	protected boolean onEnterNotify(Event e) {
		return true;
	};

	protected boolean onLeaveNotify(Event e) {
		return true;
	};

	protected boolean onKeyPressed(Event e) {
		return true;
	};

	protected boolean onKeyRepeated(Event e) {
		return true;
	};

	protected boolean onKeyReleased(Event e) {
		return true;
	};

	protected abstract boolean onExpose(Event e);

	protected boolean onShow(Event e) {
		return true;
	};

	protected boolean onHide(Event e) {
		return true;
	};

	abstract Rectangle getRequisition();

	protected void sizeAllocate(Rectangle b) {
		this.allocation = b;
	}

	protected void startClip(Graphics g) {
		startClip(g, allocation);
	}

	protected void startClip(Graphics g, Rectangle a) {
		if (clip == null) {
			clip = new Rectangle(g.getClipX(), g.getClipY(), g.getClipWidth(),
					g.getClipHeight());
		} else {
			clip.x = g.getClipX();
			clip.y = g.getClipY();
			clip.w = g.getClipWidth();
			clip.h = g.getClipHeight();
		}
		g.setClip(a.x, a.y, a.w, a.h);
	}

	protected void endClip(Graphics g) {
		g.setClip(clip.x, clip.y, clip.w, clip.h);
	}

	protected void invalidate(Widget w) {
		// registerEvent(new Event(Event.Expose));
		if (realized) {
			if (!isRoot) {
				parent.invalidate(w);
			} else {
				screen.invalidate(w);
			}
		}
	}

	protected Event localizeEvent(Event e) {
		int childx = e.x - allocation.x;
		int childy = e.y - allocation.y;
		if (childx >= 0 && childy >= 0 && childx < allocation.w
				&& childy < allocation.h) {
			return new Event(e.type, childx, childy);
		} else {
			return null;
		}
	}

	protected void registerEvent(Event e) {

		boolean bubble = true;

		if (e.type == Event.Expose) {
			bubble = onExpose(e);
			realized = true;
		} else if (e.type == Event.PointerPressed
				|| e.type == Event.PointerDragged
				|| e.type == Event.PointerReleased) {
			if ((localizeEvent(e)) == null) {
				if (entered) {
					onLeaveNotify(e);
					entered = false;
				}
			} else {
				if (!entered) {
					onEnterNotify(e);
					entered = true;
				}
			}
			if (e.type == Event.PointerPressed)
				bubble = onPointerPressed(e);
			else if (e.type == Event.PointerDragged)
				bubble = onPointerDragged(e);
			else if (e.type == Event.PointerReleased)
				bubble = onPointerReleased(e);
		} else if (e.type == Event.LongPress)
			bubble = onLongPress(e);

		else if (e.type == Event.KeyPressed)
			bubble = onKeyPressed(e);
		else if (e.type == Event.KeyRepeated)
			bubble = onKeyRepeated(e);
		else if (e.type == Event.KeyReleased)
			bubble = onKeyReleased(e);

		else if (e.type == Event.Hide)
			bubble = onHide(e);
		else if (e.type == Event.Show)
			bubble = onShow(e);

		if (bubble && hasChildren) {
			int size = children.size();
			Widget w;
			for (int i = 0; i < size; i++) {
				w = ((Widget) children.elementAt(i));
				if (e.type == Event.PointerPressed
						|| e.type == Event.PointerDragged
						|| e.type == Event.PointerReleased) {
					w.registerEvent(e);
				} else if (e.type == Event.KeyPressed
						|| e.type == Event.KeyRepeated
						|| e.type == Event.KeyReleased) {
					w.registerEvent(e);
				}

			}
		}
	}

}